﻿using OpenCvSharp;
using System.Collections.Generic;
using System.Text;
using ZXing;
using ZXing.Common;
using ZXing.Windows.Compatibility;
using Size = OpenCvSharp.Size;

/// <summary>
/// 专门处理小块内容的垂直分行
/// </summary>
public class VerticalLineSplitter
{
    /// <summary>
    /// 输入一个小块图像，返回分行后的矩形区域
    /// </summary>
    /// <param name="blockImage">单区块ROI图像（灰度或二值图）</param>
    /// <param name="lineGap">行间距阈值（默认15像素）</param>
    /// <returns>分行后的矩形列表（相对坐标）</returns>
    public List<Rect> SplitBlockIntoLines(Mat blockImage, int lineGap = 15)
    {
        // 1. 预处理：确保二值化（白底黑字）
         Mat binary = new Mat();
        if (blockImage.Channels() > 1)
            Cv2.CvtColor(blockImage, binary, ColorConversionCodes.BGR2GRAY);
        else
            binary = blockImage.Clone();

        Cv2.Threshold(binary, binary, 0, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);

        //// 2. 垂直方向膨胀（连接同一行的内容）
        //using Mat dilated = new Mat();
        //var verticalKernel = Cv2.GetStructuringElement(
        //    MorphShapes.Rect,
        //    new Size(1, lineGap)); // 关键：垂直方向膨胀
        //Cv2.Dilate(binary, dilated, verticalKernel);
       
        //dilated.SaveImage("R:\\D.jpg");
        // 3. 连通域分析（获取行位置）
        return GetConnectedLines(binary);
    }
    public List<Rect> SplitBlockIntoLinesA(Mat blockImage, int lineGap = 15)
    {
        // 1. 预处理：确保二值化（白底黑字）
        Mat binary = new Mat();
        if (blockImage.Channels() > 1)
            Cv2.CvtColor(blockImage, binary, ColorConversionCodes.BGR2GRAY);
        else
            binary = blockImage.Clone();

        Cv2.Threshold(binary, binary, 0, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);

        //// 2. 垂直方向膨胀（连接同一行的内容）
        //using Mat dilated = new Mat();
        //var verticalKernel = Cv2.GetStructuringElement(
        //    MorphShapes.Rect,
        //    new Size(1, lineGap)); // 关键：垂直方向膨胀
        //Cv2.Dilate(binary, dilated, verticalKernel);

        //dilated.SaveImage("R:\\D.jpg");
        // 3. 连通域分析（获取行位置）
        return GetConnectedLines(binary);
    }
    private List<Rect> GetConnectedLines(Mat binaryImage)
    {
        using Mat labels = new Mat();
        using Mat stats = new Mat();
        using Mat centroids = new Mat();

        int numLabels = Cv2.ConnectedComponentsWithStats(
            binaryImage, labels, stats, centroids,
            PixelConnectivity.Connectivity8);

        // 提取行区域（跳过背景标签0）
        var lines = new List<Rect>();
        for (int i = 1; i < numLabels; i++)
        {
            lines.Add(new Rect(
                X: stats.At<int>(i, 0),
                Y: stats.At<int>(i, 1),
                Width: stats.At<int>(i, 2),
                Height: stats.At<int>(i, 3)
            ));
        }

        // 按Y坐标排序（从上到下）
        lines.Sort((a, b) => a.Y.CompareTo(b.Y));
        return lines;
    }
    #region 未用到备用
    /// <summary>
    /// 可靠的连通域分析（修复错误结果问题）
    /// </summary>
    public List<Rect> GetAccurateConnectedComponents(Mat input)
    {
        // 1. 强制转换为8UC1单通道
        using Mat processed = new Mat();
        if (input.Channels() > 1)
            Cv2.CvtColor(input, processed, ColorConversionCodes.BGR2GRAY);
        else
            input.ConvertTo(processed, MatType.CV_8UC1);

        // 2. 自适应二值化（确保文字为白色，背景为黑）
        Cv2.Threshold(processed, processed, 0, 255,
            ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);

        // 3. 连通域分析（带参数校验）
        using Mat labels = new Mat();
        using Mat stats = new Mat();
        using Mat centroids = new Mat();

        int numLabels = Cv2.ConnectedComponentsWithStats(
            processed, labels, stats, centroids,
            PixelConnectivity.Connectivity4); // 改用4连通减少误连

        // 4. 验证结果有效性
        if (numLabels <= 1) // 只有背景
            throw new Exception("未检测到有效连通域");

        // 5. 提取并过滤区域
        var components = new List<Rect>();
        for (int i = 1; i < numLabels; i++) // 跳过背景(0)
        {
            int width = stats.At<int>(i, 2);
            int height = stats.At<int>(i, 3);

            // 过滤噪点（可根据图像分辨率调整）
            if (width * height < 50)
                continue;

            components.Add(new Rect(
                stats.At<int>(i, 0),
                stats.At<int>(i, 1),
                width,
                height
            ));
        }

        return components;
    }
    #endregion

    // 判断图像是否已经是二值图像
    private static bool 是二值图像(Mat image)
    {
        // 必须是单通道图像
        if (image.Channels() != 1)
            return false;
        return true;
        // 检查所有像素值是否只有0或255
        for (int y = 0; y < image.Rows; y++)
        {
            for (int x = 0; x < image.Cols; x++)
            {
                byte pixel = image.At<byte>(y, x);
                if (pixel != 0 && pixel != 255)
                    return false;
            }
        }

        return true;
    }
    public static string 识别条形码(System.Drawing.Bitmap filename)
    {   //install-package Zxing.net
        //install-package Zxing.net.Bindings.windows.compatibility
        // 创建 ZXing.Net 的 BarcodeReader
        BarcodeReader reader = new BarcodeReader();
        // 设置字符集和识别选项
        reader.Options = new DecodingOptions
        {
            CharacterSet = "UTF-8",
            // 添加其他优化设置（可选）
            TryHarder = true,   // 更耗时但更准确
            PossibleFormats = new List<BarcodeFormat> {
                    BarcodeFormat.QR_CODE,  // 根据实际类型添加
                    BarcodeFormat.CODE_128,
                    BarcodeFormat.CODE_39,
                    BarcodeFormat.EAN_8,
                    BarcodeFormat.EAN_13
          }
        };
        // 执行识别
        Result result = reader.Decode(filename);
        return result?.Text ?? "";
    }



    public static string 题块解析(Mat tkMat, int 题号, out int 题数)
    {
        // 输出参数初始化
        题数 = 0;
        StringBuilder 结果 = new StringBuilder();

        // 参数验证
        if (tkMat == null || tkMat.Empty())
        {
            return "错误：输入图像为空";
        }

        try
        {
            Mat binary;
            // 检查是否已经是二值图像
            if (是二值图像(tkMat))
            {
                // 直接使用输入图像作为二值图像
                binary = tkMat.Clone();
            }
            else
            {
                // 转换为灰度图
                using (Mat gray = new Mat())
                {
                    Cv2.CvtColor(tkMat, gray, ColorConversionCodes.BGR2GRAY);

                    // 二值化（背景白255，前景黑0）
                    binary = new Mat();
                    Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                }
            }

            // 水平投影分割行
            List<Rect> 行列表 = 分割行(binary);
            题数 = 行列表.Count;

            // 处理每一行（即每一题）
            foreach (Rect 行区域 in 行列表)
            {
                using (Mat 行图像 = new Mat(binary, 行区域))
                {
                    char 答案 = 解析单行(行图像);
                    结果.Append($"{题号}:{答案};");
                    题号++;
                }
            }

            // 移除末尾分号并返回
            if (结果.Length > 0) 结果.Length--;
            return 结果.ToString();
        }
        catch (Exception ex)
        {
            return $"错误：{ex.Message}";
        }
        finally
        {
            // 释放资源
            tkMat?.Dispose();
        }
    }

    // 水平投影分割行
    private static List<Rect> 分割行(Mat binary)
    {
        List<Rect> 行列表 = new List<Rect>();
        int 高度 = binary.Rows;
        int 宽度 = binary.Cols;

        // 计算水平投影（黑色像素数量）
        int[] 投影 = new int[高度];

        for (int y = 0; y < 高度; y++)
        {
            for (int x = 0; x < 宽度; x++)
            {
                // 安全访问像素值
                if (binary.At<byte>(y, x) == 0)
                {
                    投影[y]++;
                }
            }
        }

        // 动态阈值：有效行至少包含5%宽度的黑色像素
        int 阈值 = Math.Max(1, 宽度 / 20);
        bool 在行内 = false;
        int 行顶 = 0;

        for (int y = 0; y < 高度; y++)
        {
            if (投影[y] >= 阈值)
            {
                if (!在行内)
                {
                    在行内 = true;
                    行顶 = y;
                }
            }
            else if (在行内)
            {
                在行内 = false;
                行列表.Add(new Rect(0, 行顶, 宽度, y - 行顶));
            }
        }

        // 处理最后一行
        if (在行内)
        {
            行列表.Add(new Rect(0, 行顶, 宽度, 高度 - 行顶));
        }

        return 行列表;
    }

    // 解析单行图像
    private static char 解析单行(Mat 行图像)
    {
        int 宽度 = 行图像.Cols;
        int 高度 = 行图像.Rows;

        // 划分选项区域（忽略左侧20%的题号区域）
        int 选项起始 = (int)(宽度 * 0.2);
        int 选项宽度 = (宽度 - 选项起始) / 4;
        Rect[] 选项框 = new Rect[4];
        for (int i = 0; i < 4; i++)
        {
            int x = 选项起始 + i * 选项宽度;
            选项框[i] = new Rect(x, 0, 选项宽度, 高度);
        }

        // 检测每个选项的涂黑状态
        double[] 黑色比例 = new double[4];
        for (int i = 0; i < 4; i++)
        {
            using (Mat 选项区域 = new Mat(行图像, 选项框[i]))
            {
                黑色比例[i] = 计算黑色比例(选项区域);
            }
        }

        // 确定答案（涂黑比例最高且超过阈值）
        char 选项标签 = 'N'; // 默认未涂
        double 最大比例 = 0;
        double 阈值 = 0.3; // 涂黑判定阈值（30%）

        for (int i = 0; i < 4; i++)
        {
            if (黑色比例[i] > 最大比例 && 黑色比例[i] > 阈值)
            {
                最大比例 = 黑色比例[i];
                选项标签 = (char)('A' + i);
            }
        }

        return 选项标签;
    }

    // 计算区域内黑色像素比例（安全访问版本）
    private static double 计算黑色比例(Mat region)
    {
        int 总像素 = region.Rows * region.Cols;
        if (总像素 == 0) return 0.0;

        int 黑色像素 = 0;

        // 使用安全的方式遍历像素
        for (int y = 0; y < region.Rows; y++)
        {
            for (int x = 0; x < region.Cols; x++)
            {
                // 直接访问像素值
                if (region.At<byte>(y, x) == 0)
                {
                    黑色像素++;
                }
            }
        }

        return (double)黑色像素 / 总像素;
    }
}